package com.filetransfer.filedownloader.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;

import org.apache.log4j.Logger;

import com.filetransfer.domain.FileInfo;
import com.filetransfer.domain.FileInfoHolder;
import com.filetransfer.domain.FileTransferMessage;
import com.filetransfer.domain.Messages;
import com.filetransfer.filedownloader.domain.FileCache;
import com.filetransfer.filedownloader.domain.FileCacheStatus;
import com.filetransfer.filedownloader.domain.FileDownloadSession;

public class FileDownloader {

	private int serverPort;

	public int getServerPort() {
		return serverPort;
	}


	public void setServerPort(int serverPort) {
		this.serverPort = serverPort;
	}
	private int[] senderPorts;

	private int selfPort;
	private FileInfoHolder holder;
	private String fileDir;
	private List<FileInfo> fileList = null;

	private Socket requestSocket;
	private ObjectOutputStream out;
	private ObjectInputStream in;
	private boolean connected = false;
	private static Logger logger = Logger.getLogger(FileDownloader.class);
	private FileDownloadSession session = null;
	private FileCache currentFileCache = null;


	public FileCache getCurrentFileCache() {
		return currentFileCache;
	}


	public void setCurrentFileCache(FileCache currentFileCache) {
		this.currentFileCache = currentFileCache;
	}


	public int[] getSenderPorts() {
		return senderPorts;
	}


	public void setSenderPorts(int[] senderPorts) {
		this.senderPorts = senderPorts;
	}


	public int getSelfPort() {
		return selfPort;
	}


	public void setSelfPort(int selfPort) {
		this.selfPort = selfPort;
	}


	public String getFileDir() {
		return fileDir;
	}


	public void setFileDir(String fileDir) {
		this.fileDir = fileDir;
	}


	public List<FileInfo> getFileList() {
		return fileList;
	}


	public void setFileList(List<FileInfo> fileList) {
		this.fileList = fileList;
	}


	/**
	 *
	 * @return
	 */
	public FileInfoHolder getHolder(){
		return holder;
	}


	/**
	 *
	 * @param info
	 */
	public void setHolder(FileInfoHolder info){
		this.holder = info;
	}

	/*
	 * Start as many threads as there are number of servers listening. --> only when download
	 * command is issued with the file name as argument.
	 *
	 * when pause command is issued, need to kill all threads, save to session.
	 * The session object will be a list of child class of FileInfo object
	 * with the place holders for Array of ChunkData objects. only store chunkdata
	 * objs of the chunks that are downloaded. if they are not, then can get the difference from the
	 * numchunks etc.
	 *
	 */

	private boolean initConnection(){
		if(!connected || requestSocket == null || requestSocket.isClosed() || !requestSocket.isConnected()){
			try{
				//1. creating a socket to connect to the server
				requestSocket = new Socket("localhost", serverPort);
				//2. get Input and Output streams
				out = new ObjectOutputStream(requestSocket.getOutputStream());
				out.flush();
				in = new ObjectInputStream(requestSocket.getInputStream());
				//3: Communicating with the server
				connected = true;

			}catch(Exception e){
				logger.error("Exception in the init of FileDownloader:" + e.getMessage());
				e.printStackTrace();
				connected = false;
			}
		}
		return connected;
	}


	/**
	 * Called in the constructor/argument validator.
	 */
	public void initConfiguration(){
		initFileDownloadSession();
	}


	/**
	 * Restores the FileDownloadSession file from the HDD from fileDir/cache folder.
	 * If no session file can be found, then creates a new one.
	 */
	private void initFileDownloadSession(){
		ObjectInputStream in = null;

		/*
		 * Try to look up if an existing session cache object is present.
		 */
		try{
			in = new ObjectInputStream(new FileInputStream(fileDir+File.separator+"cache\\FILEDOWNLOADSESSION.CACHE"));
			Object obj = in.readObject();
			if(obj instanceof FileDownloadSession){
				session = (FileDownloadSession)obj;
			}
		}catch(Exception e){
			logger.error("Exception while resuming the file download session:" + e.getMessage());
			e.printStackTrace();
		}

		/*
		 * if not present, create a new session object.
		 */
		if(session == null){
			session = new FileDownloadSession();
		}
	}



	/**
	 * To be invoked only in the case of exit. sets the chunks of the FileCache
	 * that are currently in the state of inprogress to not_downloaded
	 * checks if there are FileCache obj with status not completed but
	 * with all its chunks are downloaded. calls the file assembler for such FileCache objects
	 * - the above step is performed by the ChunkDownloaderThread that downloads
	 * the last chunk.
	 *
	 */
	private void cleanUpAndSaveFileDownloadSession(){

	}

	/**
	 *
	 */
	public void getFileInfoList(){
		boolean sendSuccess = false;
		FileTransferMessage messg = null;
		/*
		 * Connect to the server's port.
		 * send Filelistrequest.
		 * get message -> terminate connection.
		 * print the list.
		 */
		try{
			//send file-list request Message
			sendSuccess = sendFileListRequest();

			//wait for the input, only if the send was successful
			//process response accordingly. no need to respond back.
			if(sendSuccess){
				messg = (FileTransferMessage)in.readObject();
				processMessage(messg);
				//validate and process the message
			}else{
				System.out.println("Error while sending FileListRequest!");
				logger.error("Error while sending FileListRequest!");
			}

		}
		catch(UnknownHostException unknownHost){
			System.err.println("You are trying to connect to an unknown host!");
		}
		catch(IOException ioException){
			ioException.printStackTrace();
		}catch(Exception e){
			e.printStackTrace();
			logger.error("Exception in the getFileList() of downloader:"+e.getMessage());
		}
		finally{
			//4: Closing connection
			try{
				in.close();
				out.close();
				requestSocket.close();
			}
			catch(IOException ioException){
				ioException.printStackTrace();
			}
		}
	}



	/**
	 *
	 */
	public void getFileInfoAndDownload(String fileName){
		boolean sendSuccess = false;
		FileTransferMessage messg = null;
		/*
		 * Connect to the server's port.
		 * send Filelistrequest.
		 * get message -> terminate connection.
		 * print the list.
		 */
		try{
			//send file-list request Message
			sendSuccess = sendFileInfoRequest(fileName);

			//wait for the input, only if the send was successful
			//process response accordingly. no need to respond back.
			if(sendSuccess){
				messg = (FileTransferMessage)in.readObject();


				//validate and process the message
				processMessage(messg);

				System.out.println("Downloading the file.");
			}

		}
		catch(UnknownHostException unknownHost){
			System.err.println("You are trying to connect to an unknown host!");
		}
		catch(IOException ioException){
			ioException.printStackTrace();
		}catch(Exception e){
			e.printStackTrace();
			logger.error("Exception in the getFileList() of downloader:"+e.getMessage());
		}
		finally{
			//4: Closing connection
			try{
				in.close();
				out.close();
				requestSocket.close();
			}
			catch(IOException ioException){
				ioException.printStackTrace();
			}
		}
	}



	/**
	 *
	 * @return
	 */
	private boolean sendFileListRequest(){
		boolean success = false;

		FileTransferMessage messg = new FileTransferMessage(Messages.FILE_LIST_REQUEST);
		try{
			if(initConnection()){
				out.writeObject(messg);
				out.flush();
				success = true;
			}
		}catch(Exception e){
			logger.error("Exception in sendFileRequest:" + e.getMessage());
			e.printStackTrace();
		}
		return success;
	}


	/**
	 *
	 * @return
	 */
	private boolean sendFileInfoRequest(String fileName){
		boolean success = false;

		FileTransferMessage messg = new FileTransferMessage(Messages.FILE_INFO_REQUEST);

		/*
		 * Check if this name is present in the list of files.
		 * first refresh the list of files.
		 */
		getFileInfoList();
		FileInfo temp = new FileInfo(fileName,0);
		if(!fileList.contains(temp)){
			System.out.println("Invalid file :" + fileName + ", not present on the server.");
			return success;
		}
		messg.setFileInfo(temp);

		try{
			if(initConnection()){
				out.writeObject(messg);
				out.flush();
				success = true;
			}
		}catch(Exception e){
			logger.error("Exception in sendFileRequest:" + e.getMessage());
			e.printStackTrace();
		}
		return success;
	}
	/**
	 *
	 * @param messg
	 */
	private void processMessage(FileTransferMessage input){

		/*
		 * incase of FileListResponse, just print the list.
		 * for FileInfoResponse, start the chunkDownloaderThreads.
		 */
		if(input != null && !input.isError()){

			if(input.getMessageType() == Messages.FILE_LIST_RESPONSE){
				/*
				 * Set the latest file list to class var
				 */
				this.fileList = input.getFileList();

			}else if(input.getMessageType() == Messages.FILE_INFO_RESPONSE){
//				System.out.println("processing file info response");
				/*
				 * print the file info
				 */
				System.out.println("The info of the file from server:"+input.getFileInfo().toStringDetailed());
				FileCache cache = session.getFileCache(input.getFileInfo());
				/*
				 * LOGIC - BRIEF
				 * Need to add this file to the FileDownloadSession's FileCache list
				 * (if already present, then replace it if the numchunks or chunk size
				 * or file size has changed)
				 * Then start the ChunkDownloaderThreads to download the chunks
				 * and update the FileCache accordingly.
				 */

				/*
				 * 1. search the FileDownloadSession for this FileInfo's corresponding FileCache object
				 *
				 * 2.perform the below validations on the FileCache object.
				 * 		i. if cache == null, then this file is being downloaded for the first time.
				 * 		create the file cache object and add it to the session (creation of filecache
				 * 		will include creation of the chunkdata array, setting correct status etc.
				 *
				 * 		ii. if cache != null and cache.status == downloaded, then prompt the user with the message
				 * 		that the file is already downloaded, does he want to download again. if yes, then reset
				 * 		the cache object.
				 *
				 * 		iii. if cache != null and cache.status == in progress, then calculate the % downloaded
				 * 		based on the number of chunks with downloaded status and number of chunks with not downloaded
				 *		status. print resuming download.
				 *
				 *		Once we have the appropriate filecache object obtained and set back into the list(incase
				 *		of new downloads), start the chunk downloader threads with the session and fileinfo object
				 *		and the chunk uploader thread's ports assigned to them.
				 *		the threads will fetch the filecache object and get the first available chunk and request
				 *		it from the chunkuploader threads.
				 *
				 *		Rest of the downloading logic will be noted in the chunk downloader threads.
				 *		a status thread can be written which can keep displaying the status based on the getFileCache
				 *		synchronized call.
				 */
				if(cache == null){
					cache = new FileCache(input.getFileInfo().getFileName(), input.getFileInfo().getFileSize(), input.getFileInfo().getNumChunks(), input.getFileInfo().getChunkSize());
					cache.setStatus(FileCacheStatus.IN_PROGRESS);
					session.setFileCache(cache);
				}else if(cache != null && cache.getStatus() == FileCacheStatus.COMPLETED){

					//as of now just print already downloaded. can add the redownload feature later on.
					System.out.println("This file is already downloaded.");

				}else if(cache != null && cache.getStatus() == FileCacheStatus.IN_PROGRESS){
					System.out.println("Resuming download for the file. current status :" + cache.getPercentageDownloaded() +"% downloaded.");
				}

			}

		}

	}

	/**
	 *
	 * @param cache
	 */
	private void startDownloaderThreads(FileCache cache){

		/*
		 * The threads will have ended themselves from the previous download once the FileCache gives
		 * null for firstundownloaded chunk. so, just start them right away.
		 */

	}

}
